package com.wangtianxiang.rabbitmq.config;

import com.wangtianxiang.rabbitmq.constants.RabbitMqConfigConstants;
import com.wangtianxiang.rabbitmq.domain.Order;
import com.wangtianxiang.rabbitmq.exception.DynamicRabbitConfiguredFailException;
import com.wangtianxiang.rabbitmq.factory.RabbitTemplateFactory;
import com.wangtianxiang.rabbitmq.factory.RetryTemplateFactory;
import com.wangtianxiang.rabbitmq.scope.RefreshScope;
import com.wangtianxiang.rabbitmq.utils.SpringBeanUtils;
import org.mockito.internal.matchers.Or;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionNameStrategy;
import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
import org.springframework.boot.autoconfigure.amqp.RabbitRetryTemplateCustomizer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import java.time.Duration;
import java.util.Date;
import java.util.stream.Collectors;


@Configuration
@ConditionalOnClass(RabbitTemplateFactory.class)
@ConditionalOnProperty(name = RabbitMqConfigConstants.ENABLED, matchIfMissing = true)
@EnableConfigurationProperties(DynamicRabbitProperties.class)
public class DynamicRabbitAutoConfiguration {

    @Bean("rabbitTemplateFactory")
    @Primary
    public RabbitTemplateFactory rabbitTemplateFactory(DynamicRabbitProperties dynamicRabbitProperties){
        return new RabbitTemplateFactory(dynamicRabbitProperties.getDataSources());
    }

//
//    @Bean
//    @ConditionalOnMissingBean
//    public RabbitMqBeanDefinitionRegistryPostProcessor rabbitMqBeanDefinitionRegistryPostProcessor(){
//        return new RabbitMqBeanDefinitionRegistryPostProcessor(RabbitMqConfigConstants.CONFIG_PREFIX);
//    }

/*
    @Bean("rabbitTemplateFactory")
    @Primary
    public RabbitTemplateFactory rabbitTemplateFactory(ApplicationContext context,DynamicRabbitProperties dynamicRabbitProperties,
                                                       ObjectProvider<RabbitRetryTemplateCustomizer> retryTemplateCustomizers,
                                                       ObjectProvider<MessageConverter> messageConverter,ObjectProvider<ConnectionNameStrategy> connectionNameStrategy)
                                                        {

            dynamicRabbitProperties.getDataSources().forEach((name, properties) -> {
                try {
                    CachingConnectionFactory connectionFactory = rabbitConnectionFactory(properties, connectionNameStrategy);

                    RabbitTemplate rabbitTemplate = rabbitTemplate(properties, connectionFactory,retryTemplateCustomizers,messageConverter);

                    SpringBeanUtils.registerBean(name + RabbitMqConfigConstants.BEAN_TEMPLATE_SUFFIX,rabbitTemplate);
                }catch (Exception e){
                    throw new DynamicRabbitConfiguredFailException("dynamicRabbit configured fail",e);
                }

            });


            return new RabbitTemplateFactory();


    }

    public RabbitTemplate rabbitTemplate(CusRabbitProperties cusRabbitProperties,ConnectionFactory connectionFactory,
                                         ObjectProvider<RabbitRetryTemplateCustomizer> retryTemplateCustomizers,
                                         ObjectProvider<MessageConverter> objectProvider) throws Exception{

        PropertyMapper map = PropertyMapper.get();
        RabbitTemplate template = new RabbitTemplate(connectionFactory);

        if(cusRabbitProperties.isPublisherConfirms()){
            template.setConfirmCallback((RabbitTemplate.ConfirmCallback) Class.forName(cusRabbitProperties.getConfirmCallback()).newInstance());
        }

        if(cusRabbitProperties.isPublisherReturns()){
            template.setReturnCallback((RabbitTemplate.ReturnCallback) Class.forName(cusRabbitProperties.getReturnCallback()).newInstance());
        }


        ObjectProvider<MessageConverter> bean = SpringBeanUtils.getBeanProvider(MessageConverter.class);


        MessageConverter messageConverter = bean.getIfUnique();
        if (messageConverter != null) {
            template.setMessageConverter(messageConverter);
        }
        template.setMandatory(determineMandatoryFlag(cusRabbitProperties));
        RabbitProperties.Template properties = cusRabbitProperties.getTemplate();
        if (properties.getRetry().isEnabled()) {
            template.setRetryTemplate(new RetryTemplateFactory(
                    retryTemplateCustomizers.orderedStream().collect(Collectors.toList())).createRetryTemplate(
                    properties.getRetry(), RabbitRetryTemplateCustomizer.Target.SENDER));
        }
        map.from(properties::getReceiveTimeout).whenNonNull().as(Duration::toMillis)
                .to(template::setReceiveTimeout);
        map.from(properties::getReplyTimeout).whenNonNull().as(Duration::toMillis).to(template::setReplyTimeout);
        map.from(properties::getExchange).to(template::setExchange);
        map.from(properties::getRoutingKey).to(template::setRoutingKey);
        map.from(properties::getDefaultReceiveQueue).whenNonNull().to(template::setDefaultReceiveQueue);
        return template;
    }


    private boolean determineMandatoryFlag(CusRabbitProperties properties) {
        Boolean mandatory = properties.getTemplate().getMandatory();
        return (mandatory != null) ? mandatory : properties.isPublisherReturns();
    }

    public CachingConnectionFactory rabbitConnectionFactory(CusRabbitProperties properties,
                                                            ObjectProvider<ConnectionNameStrategy> connectionNameStrategy){
        PropertyMapper map = PropertyMapper.get();
        CachingConnectionFactory factory = null;
        try {
            factory = new CachingConnectionFactory(
                    getRabbitConnectionFactoryBean(properties).getObject());
        } catch (Exception e) {
            e.printStackTrace();
        }
        map.from(properties::determineAddresses).to(factory::setAddresses);
        map.from(properties::isPublisherConfirms).to(factory::setPublisherConfirms);
        map.from(properties::isPublisherReturns).to(factory::setPublisherReturns);
        RabbitProperties.Cache.Channel channel = properties.getCache().getChannel();
        map.from(channel::getSize).whenNonNull().to(factory::setChannelCacheSize);
        map.from(channel::getCheckoutTimeout).whenNonNull().as(Duration::toMillis)
                .to(factory::setChannelCheckoutTimeout);
        RabbitProperties.Cache.Connection connection = properties.getCache().getConnection();
        map.from(connection::getMode).whenNonNull().to(factory::setCacheMode);
        map.from(connection::getSize).whenNonNull().to(factory::setConnectionCacheSize);
        map.from(connectionNameStrategy::getIfUnique).whenNonNull().to(factory::setConnectionNameStrategy);
        return factory;
    }

    private RabbitConnectionFactoryBean getRabbitConnectionFactoryBean(RabbitProperties properties)
            throws Exception {
        PropertyMapper map = PropertyMapper.get();
        RabbitConnectionFactoryBean factory = new RabbitConnectionFactoryBean();
        map.from(properties::determineHost).whenNonNull().to(factory::setHost);
        map.from(properties::determinePort).to(factory::setPort);
        map.from(properties::determineUsername).whenNonNull().to(factory::setUsername);
        map.from(properties::determinePassword).whenNonNull().to(factory::setPassword);
        map.from(properties::determineVirtualHost).whenNonNull().to(factory::setVirtualHost);
        map.from(properties::getRequestedHeartbeat).whenNonNull().asInt(Duration::getSeconds)
                .to(factory::setRequestedHeartbeat);
        RabbitProperties.Ssl ssl = properties.getSsl();
        if (ssl.isEnabled()) {
            factory.setUseSSL(true);
            map.from(ssl::getAlgorithm).whenNonNull().to(factory::setSslAlgorithm);
            map.from(ssl::getKeyStoreType).to(factory::setKeyStoreType);
            map.from(ssl::getKeyStore).to(factory::setKeyStore);
            map.from(ssl::getKeyStorePassword).to(factory::setKeyStorePassphrase);
            map.from(ssl::getTrustStoreType).to(factory::setTrustStoreType);
            map.from(ssl::getTrustStore).to(factory::setTrustStore);
            map.from(ssl::getTrustStorePassword).to(factory::setTrustStorePassphrase);
            map.from(ssl::isValidateServerCertificate)
                    .to((validate) -> factory.setSkipServerCertificateValidation(!validate));
            map.from(ssl::getVerifyHostname).to(factory::setEnableHostnameVerification);
        }
        map.from(properties::getConnectionTimeout).whenNonNull().asInt(Duration::toMillis)
                .to(factory::setConnectionTimeout);
        factory.afterPropertiesSet();
        return factory;
    }*/




//    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }


}
